package com.ucpaas.paas.web.common.interceptor;


import com.ucpaas.paas.domain.dto.Page;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;


/**
 * 使用spring-boot不需要再配置Interceptor，因为Intercepts会被扫描到从而自动配置到mybatis里
 *
 * @author taoj
 */
@Component
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class})})
public class PageInterceptor implements Interceptor {

    private static final String PAGE_FIELD_NAME = "page";
    private static Logger logger = LoggerFactory.getLogger(PageInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
//            logger.debug("进入分页器");
            // 当前环境 MappedStatement，BoundSql，及sql取得
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = invocation.getArgs()[1];
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String originalSql = boundSql.getSql().trim();
            Object parameterObject = boundSql.getParameterObject();

            // Page对象获取，“信使”到达拦截器！
//            Page page = searchPageWithXpath(boundSql.getParameterObject(), ".", "page", "*/page");
            Page page = null;
            if (parameterObject != null) {
                page = convertParameter(parameterObject);
            }


            if (page != null) {
                // Page对象存在的场合，开始分页处理
                String countSql = getCountSql(originalSql);
                logger.debug("执行countSql:" + countSql);
                Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource()
                        .getConnection();
                PreparedStatement countStmt = connection.prepareStatement(countSql);
                BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql);
                DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject,
                        countBS);
                parameterHandler.setParameters(countStmt);
                ResultSet rs = countStmt.executeQuery();
                int totpage = 0;
                if (rs.next()) {
                    totpage = rs.getInt(1);
                }
                rs.close();
                countStmt.close();
                connection.close();

                // 分页计算
                page.setTotalRecord(totpage);


                int offset = (page.getPage() - 1) * page.getRows();
                StringBuffer sb = new StringBuffer();
                sb.append(originalSql);
                //增加排序规则
                if (!StringUtils.isEmpty(page.getOrderByClause())) {
                    sb.append(" ORDER BY ").append(page.getOrderByClause());
                }
                // 对原始Sql追加limit
                sb.append(" limit ").append(offset).append(",").append(page.getRows());
                BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, sb.toString());
                MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
                invocation.getArgs()[0] = newMs;
            }
        } catch (Exception e) {
            if (e.getMessage() != null && e.getMessage().contains("Parameter 'page' not found")) ;
            else
                logger.error("分页器异常", e);
        }
//        logger.debug("离开分页器");
        return invocation.proceed();

    }

    private Page convertParameter(Object parameterObject) {
        try {
            if (parameterObject instanceof Page) {
                return (Page) parameterObject;
            } else {
                for (Class<?> superClass = parameterObject.getClass(); superClass != Object.class; superClass = superClass
                        .getSuperclass()) {
                    try {
                        Field field = superClass.getDeclaredField(PAGE_FIELD_NAME);
                        makeAccessible(field);
                        return (Page) field.get(parameterObject);
                    } catch (NoSuchFieldException e) {// NOSONAR
                        // Field不在当前类定义,继续向上转型
                        continue;
                    }
                }
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }

    private void makeAccessible(Field field) {
        if (isAccess(field)) {
            field.setAccessible(true);
        }
    }

    /**
     * 是否 需要 设置允许访问字段
     *
     * @param field
     * @return
     */
    private boolean isAccess(Field field) {
        return (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible();
    }

    /**
     * 根据给定的xpath查询Page对象
     */
//    private Page searchPageWithXpath(Object o, String... xpaths) {
//        JXPathContext context = JXPathContext.newContext(o);
//        Object result;
//        for (String xpath : xpaths) {
//            try {
//                result = context.selectSingleNode(xpath);
//            } catch (JXPathNotFoundException e) {
//                continue;
//            }
//            if (result instanceof Page) {
//                return (Page) result;
//            }
//        }
//        return null;
//    }

    /**
     * 复制MappedStatement对象
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        Builder builder = new Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.keyProperty(fromStrArry(ms.getKeyProperties()));
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    private String fromStrArry(String[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < arr.length; i++) {
            sb.append(arr[i]);
            if (i < arr.length - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

    /**
     * 复制BoundSql对象
     */
    private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(),
                boundSql.getParameterObject());
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        return newBoundSql;
    }

    /**
     * 根据原Sql语句获取对应的查询总记录数的Sql语句
     */
    private String getCountSql(String sql) {
        return "SELECT COUNT(*) FROM (" + sql + ") aliasForPage";
    }

    @Override
    public Object plugin(Object arg0) {
        return Plugin.wrap(arg0, this);
    }

    @Override
    public void setProperties(Properties arg0) {
    }

    public class BoundSqlSqlSource implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
